package com.dstz.sys.util;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

import com.dstz.base.core.util.AppUtil;
import com.dstz.base.core.util.ExceptionUtil;
import com.dstz.sys.api.groovy.IGroovyScriptEngine;
import com.dstz.sys.core.manager.ScriptManager;
import com.dstz.sys.core.manager.SysScheduleJobLogManager;
import com.dstz.sys.core.manager.SysScheduleJobManager;
import com.dstz.sys.core.model.SysScheduleJob;
import com.dstz.sys.core.model.SysScheduleJobLog;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.TaskExecutor;
import cn.hutool.cron.listener.SimpleTaskListener;
import cn.hutool.cron.task.InvokeTask;
import cn.hutool.cron.task.RunnableTask;
import cn.hutool.cron.task.Task;
import cn.hutool.setting.Setting;

/**
 * hutool-cron定时任务调度
 */
@Component
public class ScheduleUtil implements InitializingBean, DisposableBean {
	@Autowired SysScheduleJobManager sysScheduleJobManager;
    @Autowired SysScheduleJobLogManager sysScheduleJobLogManager;
    static RedisLockRegistry redisLockRegistry;
    static String hostAndContextName = StringUtils.trimToEmpty(NetUtil.getLocalhostStr()) + "," + System.getProperty("contextName","bpmdemo") + "\n";
    private static Logger LOG = LoggerFactory.getLogger(ScheduleUtil.class);
    
    public ScheduleUtil(@Autowired(required=false) RedisLockRegistry redisLockRegistry) {
    	ScheduleUtil.redisLockRegistry = redisLockRegistry;
    }
    
	@Override
	public void destroy() throws Exception {
		if(isStarted()) CronUtil.stop();
		LOG.info("hutool-cron scheduler destroy");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		try {
			CronUtil.setCronSetting(CronUtil.CRONTAB_CONFIG_PATH);
		}catch(Exception e) {//服务器报空指针异常：ClassPath is null
			CronUtil.setCronSetting(new Setting());
		}
		CronUtil.start();
		LOG.info("hutool-cron scheduler start, redis-lock-registry: {}", redisLockRegistry!=null);
		
		CronUtil.getScheduler().addListener(new SimpleTaskListener() {
			private ThreadLocal<Date> threadLocal = new ThreadLocal<>();
			@Override
			public void onStart(TaskExecutor executor) {
				LOG.info("scheduler start job: {}", executor.getTask());
				threadLocal.set(new Date());
			}
			@Override
			public void onSucceeded(TaskExecutor executor) {
				onFailed(executor, null);
			}
			@Override
			public void onFailed(TaskExecutor executor, Throwable exception) {
		        Date startTime = threadLocal.get();
		        threadLocal.remove();
		        Task task = executor.getTask();
		        if(task instanceof JobTask) {
		        	JobTask jobTask = (JobTask)task;
		        	if(jobTask.execute) {
			        	SysScheduleJob sysScheduleJob = jobTask.getJob();
			        	final SysScheduleJobLog sysScheduleJobLog = new SysScheduleJobLog();
			        	sysScheduleJobLog.setJobId(sysScheduleJob.getId());
			        	sysScheduleJobLog.setStartTime(startTime);
			        	sysScheduleJobLog.setEndTime(new Date());
			        	sysScheduleJobLog.setRunMs(sysScheduleJobLog.getEndTime().getTime() - sysScheduleJobLog.getStartTime().getTime());
			        	if (exception != null) {
			        		LOG.info("scheduler fail job: {}", executor.getTask());
			        		sysScheduleJobLog.setRunState("FAIL");
			        		sysScheduleJobLog.setContent(hostAndContextName + ExceptionUtil.getExceptionMessage(exception));
			        	} else {
			        		LOG.info("scheduler success job: {}", executor.getTask());
			        		sysScheduleJobLog.setRunState("SUC");
			        		sysScheduleJobLog.setContent(hostAndContextName);
			        	}
			        	sysScheduleJobLogManager.create(sysScheduleJobLog);
		        	}
		        }
			}
		});
		
		RedisPubsub.sub(SysScheduleJob.class, job -> {
			boolean enable = SysScheduleJob.ENABLE.equals(job.getRunningState());
			if(StringUtils.isBlank(job.getId())) {
				//不传id时，控制整个定时任务功能，参考SysScheduleJobController.enableAll方法
				ScheduleUtil.enableAll(enable);
			}else if(enable) {
				ScheduleUtil.schedule(job);
			}else {
				ScheduleUtil.deschedule(job.getId());
			}
		});
		
		List<SysScheduleJob> jobs = sysScheduleJobManager.getAll();
		for(SysScheduleJob job : jobs) {
			if("ENABLE".equals(job.getRunningState())) {
				schedule(job);
			}else {
				LOG.info("hutool-cron skip DISABLE job: {}", job.getId());
			}
		}
	}
	
	/**
	 * 转换SysScheduleJob为hutool-cron可调度的Task
	 * @param job
	 * @return
	 */
	public static JobTask jobTask(SysScheduleJob job) {
		return new JobTask(job);
	}
	
	/**
	 * 计划定时任务
	 * @param job
	 * @return
	 */
	public static boolean schedule(SysScheduleJob job) {
		try {
			Task jobTask = jobTask(job);
			LOG.info("hutool-cron schedule job: {}", jobTask);
			CronUtil.schedule(job.getId(), job.getCronExpression(), jobTask);
			return true;
		}catch(Exception e) {
			LOG.warn("fail to schedule job: {}, ex: {}, msg: {}", job.getId(), e.getClass().getSimpleName(), e.getMessage());
			return false;
		}
	}
	
	/**
	 * 取消定时任务
	 * @param id
	 * @return
	 */
	public static boolean deschedule(String id) {
		Task task = CronUtil.getScheduler().getTask(id);
		LOG.info("hutool-cron deschedule job: {}", task);
		CronUtil.getScheduler().deschedule(id);
		return task != null;
	}
	
	/** 判断定时任务是否启用 */
	public static boolean isStarted() {
		return CronUtil.getScheduler().isStarted();
	}
	
	/** 控制定时任务功能 */
	public static boolean enableAll(boolean enable) {
		LOG.info("hutool-cron enableAll: {}", enable);
		if(enable) {
			if(isStarted()==false) {
				CronUtil.getScheduler().start();
				return true;
			}
		}else {
			if(isStarted()) {
				CronUtil.getScheduler().stop();
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 转换job为可执行的Task
	 * @param job
	 * @return
	 */
	public static Task delegate(JobTask jobTask) {
		SysScheduleJob job = jobTask.getJob();
		String invokeTarget = job.getInvokeTarget();
		Task task = null;
		if("script".equalsIgnoreCase(invokeTarget)) {
			task = new ScriptTask(jobTask.getJob().getDescription());
		}
		if(task == null) {
			try {//尝试class#method
				task = new InvokeTask(invokeTarget);
			}catch(Exception e) {}
		}
		if(task == null) {
			try{//尝试runnable callable
				Class<?> clazz = ClassLoaderUtil.loadClass(invokeTarget);
				Object obj = ReflectUtil.newInstanceIfPossible(clazz);
				if(obj instanceof Runnable) task = new RunnableTask((Runnable)obj);
			}catch(Exception e) {}
		}
		if(task == null) {
			LOG.warn("fail to delegate job: {} invokeTarget: {}", jobTask, invokeTarget);
		}
		return task;
	}
	
	/** 包装SysScheduleJob为Task */
	public static class JobTask implements Task {
		private SysScheduleJob job;
		private Task task;
		private int executes = 0;
		private boolean execute = false;
		//在前10秒内完成加锁，通过executes次数来模拟轮询
		private static int total = 10, timeout = 5; 
		public JobTask(SysScheduleJob job) {
			this.job = job;
		}
		public SysScheduleJob getJob() {
			return job;
		}
		@Override
		public String toString() {
			return job.getId()+"="+job.getCronExpression();
		}
		@Override
		public void execute() {
			execute = job.getConcurrent()!=null && job.getConcurrent().booleanValue();
			if(execute == false) {
				if(redisLockRegistry==null) {
					execute = true;
				}else {
					String lockKey = SysScheduleJob.class.getSimpleName()+"."+job.getId();
					try{
						Lock lock = redisLockRegistry.obtain(lockKey);
						//模拟轮询，被调度的次数越多，获取锁的几率越小，默认30秒后自动解锁
						if(executes>0) ThreadUtil.safeSleep(TimeUnit.SECONDS.toMillis(executes));
						execute = lock.tryLock(timeout, TimeUnit.SECONDS);//tryLock()总是返回false，因此限时5秒
						if(execute) executes = (executes+1) % total;
						LOG.info("scheduler balance job: {}, execute: {}, executes: {}, host: {}", this, execute, executes, hostAndContextName);
						redisLockRegistry.expireUnusedOlderThan(TimeUnit.SECONDS.toMillis(30));
					}catch(Exception e) {
						LOG.warn("scheduler balance fail job: {} ex: {} msg: {}", this, e.getClass().getSimpleName(), e.getMessage());
						redisLockRegistry = null;
						execute = true;
					}
				}
			}
			if(execute) {
				if(task != null) {
					task.execute();
				}else {
					task = ScheduleUtil.delegate(this);
					//解析成功时执行定时任务，解析失败时禁止定时任务
					if(task != null) {
						task.execute();
					}else {
						CronUtil.getScheduler().deschedule(job.getId());
					}
				}
			}
		}
	}
	
	/** 脚本script代理任务 */
	public static class ScriptTask implements Task {
		private String script;
		private IGroovyScriptEngine groovyScriptEngine;
		public ScriptTask(String script) {
			this.script = script;
		}
		@Override
		public void execute() {
			if(groovyScriptEngine==null) {
				groovyScriptEngine = AppUtil.getBean(IGroovyScriptEngine.class);
			}
			if(StringUtils.isNumeric(script)) {
				ScriptManager scriptManager = AppUtil.getBean(ScriptManager.class);
				script = scriptManager.get(script).getScript();
			}
			groovyScriptEngine.execute(script);
		}
	}
}
